﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint.Portal.WebControls;
using System.Web.UI;
using System.Web.UI.WebControls;
using SPAdvancedSearch.Utility;
using System.ComponentModel;
using Microsoft.SharePoint.WebPartPages;
using System.Collections;

namespace SPAdvancedSearch
{
    /// <summary>
    /// Defines a web part that provides an expandable advanced search user interface.
    /// </summary>
    public class AdvancedSearchWebPart : SearchBoxEx
    {

        #region Enumerations

        /// <summary>
        /// Defines the possible toggle modes for the advanced search panel.
        /// </summary>
        private enum PanelToggleMode
        {
            Toggle = 0,
            Hide = 1,
            Show = 2
        }

        #endregion

        #region Constants

        private const string HIDDEN_PANEL_STATUS_INPUT = "panelStatusInput";
        private const string ARROW_DOWN = "/_layouts/images/blk_dwn.gif";
        private const string ARROW_RIGHT = "/_layouts/images/blk_rgt.gif";

        #endregion

        #region Fields

        private AdvancedSearchPanel _advancedSearchPanel;
        private Image _toggleImage;
        private string _searchPropertyXml = string.Empty;
        private List<SearchProperty> _searchProperties;

        #endregion

        #region Ctor

        /// <summary>
        /// Creates an instance of <see cref="AdvancedSearchWebPart"/>.
        /// </summary>
        public AdvancedSearchWebPart()
            : base()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the arrow image that is toggled based on the state of the panel.
        /// </summary>
        private Image ToggleImage
        {
            get
            {
                if (_toggleImage == null)
                {
                    _toggleImage = new Image();
                    _toggleImage.ID = "advSearchArrow";
                    _toggleImage.ImageUrl = ARROW_RIGHT;
                }
                return _toggleImage;
            }
        }
        /// <summary>
        /// Gets the advanced search panel.
        /// </summary>
        private AdvancedSearchPanel AdvancedSearchPanel
        {
            get
            {
                if (_advancedSearchPanel == null)
                {

                    _advancedSearchPanel = new AdvancedSearchPanel(this.SearchProperties, this.m_searchKeyWordTextBox);
                    _advancedSearchPanel.CssClass = "srch-options ms-sbtable-border";
                    _advancedSearchPanel.ShowPanel(false);
                }

                return _advancedSearchPanel;
            }
        }

        /// <summary>
        /// Gets or sets the xml representation of the search properties.
        /// </summary>
        [Browsable(true), 
        FriendlyName("Advanced Search Properties"), 
        Category("Advanced Search"), 
        Description("Advanced search property xml"), 
        DefaultValue("")]
        public string SearchPropertyXml
        {
            get { return _searchPropertyXml; }
            set { _searchPropertyXml = value;  }
        }

        /// <summary>
        /// Gets the advanced search properties defined for the web part.
        /// </summary>
        /// <returns></returns>
        [Browsable(false)]
        public List<SearchProperty> SearchProperties
        {
            get
            {
                if (this.SearchPropertyXml.Length > 0)
                {
                    //TODO: handle invalid hydration
                    _searchProperties = (List<SearchProperty>)SerializationManager.HydrateObjectFromXml(typeof(List<SearchProperty>), this.SearchPropertyXml);
                }
                else
                {
                    _searchProperties = new List<SearchProperty>();
                }

                return _searchProperties;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Informs the web part framework to ignore serialization of the SearchProperties.
        /// </summary>
        /// <returns></returns>
        public bool ShouldSerializeSearchProperties()
        {
            return false;
        }

        /// <summary>
        /// Overridden to add the advanced search web part tool part.
        /// </summary>
        /// <returns></returns>
        public override Microsoft.SharePoint.WebPartPages.ToolPart[] GetToolParts()
        {
            ToolPart[] toolParts = base.GetToolParts();
            ArrayList allToolParts = new ArrayList();

            // add the advanced search toolpart
            AdvancedSearchToolPart advancedSearchToolPart = new AdvancedSearchToolPart();
            allToolParts.Add(advancedSearchToolPart);

            // add the additional toolparts defined by the base classes
            foreach (ToolPart tp in toolParts)
            {
                allToolParts.Add(tp);
            }

            return (ToolPart[])allToolParts.ToArray(typeof(ToolPart));
        }

        /// <summary>
        /// Overridden to create the advanced search panel.
        /// </summary>
        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            this.Controls.Add(this.AdvancedSearchPanel);

            Page.ClientScript.RegisterHiddenField(HIDDEN_PANEL_STATUS_INPUT, "0");
        }

        /// <summary>
        /// Overridden to adjust the advanced search hyperlink.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            try
            {
                QueryStringParser parser = new QueryStringParser(Page.Request.QueryString["k"]);

                this.OverrideBaseSearchInputControls();

                // register the toggle javascript
                this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "SPASToggle", GenerateAdvancedSearchPanelToggleScript(), true);

                // check to see if we have a post-back
                if (!String.IsNullOrEmpty(Page.Request.Form[HIDDEN_PANEL_STATUS_INPUT]))
                {
                    string script = GetToggleJavaScriptCall((PanelToggleMode)Convert.ToInt32(Page.Request.Form[HIDDEN_PANEL_STATUS_INPUT]));
                    Page.ClientScript.RegisterStartupScript(this.GetType(), "SPASStartup", script, true);
                }
                else if (parser.ContainsAdvancedSearchProperties(this.SearchProperties))
                {
                    // we can assume that the user used the advanced search panel to perform an advanced search... show the panel.
                    string script = GetToggleJavaScriptCall(PanelToggleMode.Show);
                    Page.ClientScript.RegisterStartupScript(this.GetType(), "SPASStartup", script, true);

                    // remove the advanced search properties from the OOTB search input
                    this.ExcludeAdvancedPropertiesFromSearchInput();
                }
            }
            catch (Exception ex)
            {
                //TODO: who likes to do error handling anyway :)
                Page.Response.Write(ex.Message);
            }
        }

        /// <summary>
        /// Removes the managed properties used in the advanced search from the search input.
        /// </summary>
        private void ExcludeAdvancedPropertiesFromSearchInput()
        {
            QueryStringParser parser = new QueryStringParser(Page.Request.QueryString["k"]);
            string searchInputText = parser.RemoveAdvancedSearchProperties(this.m_searchKeyWordTextBox.Text);
            this.m_searchKeyWordTextBox.Text = searchInputText;
        }

        /// <summary>
        /// Overrides the base search input control behavior.
        /// </summary>
        private void OverrideBaseSearchInputControls()
        {
            // find the advanced search link
            HyperLink advancedSearchLink = this.FindControl<HyperLink>(this.Controls, typeof(HyperLink));

            //TODO: validate that it exists.
            if (advancedSearchLink != null)
            {
                advancedSearchLink.NavigateUrl = GetToggleJavaScriptCall(PanelToggleMode.Toggle);
                advancedSearchLink.Parent.Controls.AddAt(0, this.ToggleImage);
            }

            // find the go button link
            HyperLinkLoc goButtonLink = this.FindControl<HyperLinkLoc>(this.Controls, typeof(HyperLinkLoc));

            if (goButtonLink != null)
            {
                //TODO: validate that it exists
                this.AdvancedSearchPanel.OnSearchClientScript = goButtonLink.NavigateUrl;
            }
        }

        /// <summary>
        /// Gets the JavaScript call for toggling the search panel.
        /// </summary>
        /// <param name="show"></param>
        /// <returns></returns>
        private string GetToggleJavaScriptCall(PanelToggleMode toggleMode)
        {
            return string.Format("JavaScript:toggleSearchPane({0});", Convert.ToInt32(toggleMode));
        }


        /// <summary>
        /// Generates the JavaScript method for toggling the search panel.
        /// </summary>
        /// <returns></returns>
        private string GenerateAdvancedSearchPanelToggleScript()
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("function toggleSearchPane(toggleMode)");
            sb.Append("{");

            sb.AppendFormat("   var panel = document.all.{0};", this.AdvancedSearchPanel.ClientID);
            sb.AppendFormat("   var panelStatusInput = document.all.{0};", HIDDEN_PANEL_STATUS_INPUT);
            sb.AppendFormat("   var toggleImage = document.all.{0};", this.ToggleImage.ClientID);
            sb.AppendFormat("   var show = toggleMode == {0};", Convert.ToInt32(PanelToggleMode.Show));
            sb.AppendFormat("   if (toggleMode == {0})", Convert.ToInt32(PanelToggleMode.Toggle));
            sb.Append("         {");
            sb.Append("             show = panel.style.display == 'none';");
            sb.Append("         }");

            sb.Append("         panelStatusInput.value = toggleMode;");

            sb.Append("         if (show)");
            sb.Append("         {");
            sb.Append("             panel.style.display = '';");
            sb.AppendFormat("       toggleImage.src = '{0}';", ARROW_DOWN);
            sb.Append("         }");
            sb.Append("         else");
            sb.Append("         {");
            sb.Append("             panel.style.display = 'none';");
            sb.AppendFormat("       toggleImage.src = '{0}';", ARROW_RIGHT);
            sb.Append("         }");

            sb.Append("}");

            return sb.ToString();
        }
        /// <summary>
        /// Finds a control with the given type in the contained control hierarchy.
        /// </summary>
        /// <param name="controls"></param>
        /// <returns></returns>
        private T FindControl<T>(ControlCollection controls, Type type) where T : Control
        {
            T controlToFind = null;

            foreach (Control c in controls)
            {
                if (c.GetType().FullName == type.FullName)
                {
                    controlToFind = c as T;
                    break;
                }
                if (c.HasControls())
                {
                    controlToFind = this.FindControl<T>(c.Controls, type);
                    if (controlToFind != null)
                        break;
                }
            }

            return controlToFind;
        }

        #endregion
    }
}
